package tresp.medusa;

import java.awt.Color;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.PriorityQueue;
import java.util.Set;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import java.util.logging.Logger;

import robocode.BattleEndedEvent;
import robocode.BulletHitBulletEvent;
import robocode.BulletHitEvent;
import robocode.BulletMissedEvent;
import robocode.CustomEvent;
import robocode.DeathEvent;
import robocode.HitByBulletEvent;
import robocode.HitRobotEvent;
import robocode.HitWallEvent;
import robocode.MessageEvent;
import robocode.RobotDeathEvent;
import robocode.RoundEndedEvent;
import robocode.ScannedRobotEvent;
import robocode.StatusEvent;
import robocode.TeamRobot;
import robocode.WinEvent;
import tresp.bthreads.BThread;
import tresp.bthreads.BThreadType;
import tresp.bthreads.EncloseEnemyMove;
import tresp.bthreads.FactorizedMove;
import tresp.bthreads.Kill;
import tresp.bthreads.PatternMatchingTargeting;
import tresp.bthreads.StayAlive;
import tresp.bthreads.Success;
import tresp.coordinator.AbstractLevelCoordinator;
import tresp.coordinator.BThreadVoteType;
import tresp.coordinator.ICoordinator;
import tresp.utils.BThreadManager;
import tresp.utils.BThreadVote;
import tresp.utils.IEventResponder;
import tresp.utils.ITankInfo;
import tresp.utils.TankInfoImpl;
import tresp.utils.ThreadManager;
import tresp.utils.logging.MyLogger;
import tresp.utils.team.AvoidHittingFromTeamMates;
import tresp.utils.team.AvoidTeamMatesCollision;
import tresp.utils.team.TeamMateMessageListener;


/**
 * 
 * <b>Medusa</b>
 * 
 * <p>
 * 	This agent tank implements behaviorial programming methodology.
 * </p>
 * 
 * @author <font color="blue">Maxim Kirilov</font>
 *
 */
public class Medusa  extends TeamRobot implements ICoordinator<BThreadVote> {

    private static Logger logger = MyLogger.getLogger( Medusa.class );
    
    /**indicates whether this coordinator is active or not*/
    private boolean isActive = true;

    /**Holds this level b-threads votes*/
    private PriorityQueue<BThreadVote> lVotes;
    
    /**All <code>IEventResponder</code> listeners*/
    private List<IEventResponder> lEventResponders;
    
    /**Coordinators list*/
    private List<ICoordinator<BThreadVote>> lCoordinators;
    
    /**Synchronization lock*/
    private Lock synchronizationLock;
    
    /**A List of blocked votes for the current turn*/
    private Set<BThreadVote> blockedVotes;
    
    
    //B-Threads
    BThread success;
    BThread kill;
    BThread stayAlive;
    BThread patternMatcherTargeting;
    BThread encloseEnemyMove;
    BThread factorizedMove;
//    BThread predictiveTargeting;
//    BThread gueesFactorTargeting;
//    BThread waveSurfTargeting;
    BThread teamMateMessageListener;
    BThread avoidHittingFromTeamMates;
    BThread avoidTeamMatesCollision;
    
    public Medusa(){
	synchronizationLock = new ReentrantLock( true );
	lVotes = new PriorityQueue<BThreadVote>();
	lEventResponders = new ArrayList<IEventResponder>();
	lCoordinators = new ArrayList<ICoordinator<BThreadVote>>();
	blockedVotes = Collections.synchronizedSet( new HashSet<BThreadVote>() );
	init();

    }


    private void init() {
	
	//Creating Coordinators
	ICoordinator<BThreadVote> coordinatorLevel0 = new AbstractLevelCoordinator( 0 , this);
	ICoordinator<BThreadVote> coordinatorLevel1 = new AbstractLevelCoordinator( 1 , this);
	
	lCoordinators.add( coordinatorLevel0 );
	lCoordinators.add( coordinatorLevel1 );
	lCoordinators.add( this );
	
	//Creating B-Threads
	success = new Success( coordinatorLevel0 );
	kill = new Kill( coordinatorLevel1 );
	stayAlive = new StayAlive( coordinatorLevel1 );
	patternMatcherTargeting = new PatternMatchingTargeting( this );
	encloseEnemyMove = new EncloseEnemyMove( this );
	factorizedMove = new FactorizedMove( this );
//	predictiveTargeting = new PredictiveTargeting( this );
//	gueesFactorTargeting = new GueesFactorTargeting( this );
//	waveSurfTargeting = new WaveSurfTargeting( this );
	teamMateMessageListener = new TeamMateMessageListener( this );
	avoidHittingFromTeamMates = new AvoidHittingFromTeamMates( this );
	avoidTeamMatesCollision = new AvoidTeamMatesCollision( this );
	
	//Register b-threads for notifications
	lEventResponders.add( success );
	lEventResponders.add( kill );
	lEventResponders.add( stayAlive );
	lEventResponders.add( patternMatcherTargeting );
	lEventResponders.add( encloseEnemyMove );
	lEventResponders.add( factorizedMove );
//	lEventResponders.add( predictiveTargeting );
//	lEventResponders.add( gueesFactorTargeting );
//	lEventResponders.add( waveSurfTargeting );
	lEventResponders.add( teamMateMessageListener );
	lEventResponders.add( avoidHittingFromTeamMates );
	lEventResponders.add( avoidTeamMatesCollision );
	
	//Registering B-Threads
	BThreadManager.getInstance().addBthread(BThreadType.SUCCESS, success);
	BThreadManager.getInstance().addBthread(BThreadType.KILL, kill);
	BThreadManager.getInstance().addBthread(BThreadType.STAY_ALIVE, stayAlive);
	BThreadManager.getInstance().addBthread(BThreadType.PATTERN_MATCHER_TARGETING, patternMatcherTargeting);
	BThreadManager.getInstance().addBthread(BThreadType.ENCLOSE_ENEMY_MOVE, encloseEnemyMove);
	BThreadManager.getInstance().addBthread(BThreadType.FACTORIZED_MOVE, factorizedMove);
//	BThreadManager.getInstance().addBthread(BThreadType.PREDICTIVE_TARGETING, predictiveTargeting);
//	BThreadManager.getInstance().addBthread(BThreadType.GUEES_FACTOR_TARGETING, gueesFactorTargeting);
//	BThreadManager.getInstance().addBthread(BThreadType.WAVE_SURFING_TARGETING, waveSurfTargeting);
	BThreadManager.getInstance().addBthread(BThreadType.TEAM_MATE_MESSAGES_LISTENER, teamMateMessageListener);
	BThreadManager.getInstance().addBthread(BThreadType.AVOID_HITTING_FROM_TEAM_MATE, avoidHittingFromTeamMates);
	BThreadManager.getInstance().addBthread(BThreadType.AVOID_TEAM_MATE_COLLISIONS, avoidTeamMatesCollision);
	
	
	//Starting Coordinators
	ThreadManager.getInstance().submit( "Coordinator Level 0", coordinatorLevel0 );
	ThreadManager.getInstance().submit( "Coordinator Level 1", coordinatorLevel1 );

    }


    /**
     * 	The main robot's method.
     */
    @Override
    public void run(){

	//Starting B-Threads
	ThreadManager.getInstance().submit( "Success", success );
	ThreadManager.getInstance().submit( "Kill", kill );
	ThreadManager.getInstance().submit( "Stay Alive", stayAlive );
	
	ThreadManager.getInstance().submit( "Pattern Matcher Targeting", patternMatcherTargeting );
	ThreadManager.getInstance().submit( "Enclose Enemy Move", encloseEnemyMove );
	ThreadManager.getInstance().submit( "Factorized Move", factorizedMove );
//	ThreadManager.getInstance().submit( "Predictive Targeting", predictiveTargeting );
//	ThreadManager.getInstance().submit( "Guees Factor Targeting", gueesFactorTargeting );
//	ThreadManager.getInstance().submit( "Wave surfing Targeting", waveSurfTargeting );
	
	ThreadManager.getInstance().submit( "Team Mate Messages Listener", teamMateMessageListener );
	ThreadManager.getInstance().submit( "Avoid Hitting from team mates", avoidHittingFromTeamMates );
	ThreadManager.getInstance().submit( "Avoid team mates collisions", avoidTeamMatesCollision );
	
	setColors(Color.green.darker().darker(), Color.green.darker(), Color.green.darker());

	setAdjustGunForRobotTurn(true);
	setAdjustRadarForGunTurn(true);

	while( isActive ){	
	    
	    synchronizationLock.lock();
	    BThreadVote nextVote = getNextVote();
	    synchronizationLock.unlock();

	    if ( nextVote != null ) { 
		executeVote( nextVote ); 
		execute();
	    }else { 
		doNothing(); 
	    }
	    
	}

    }



    private void executeVote(BThreadVote nextAction) {
	
//	logger.info("Executing: "+ nextAction.getVoteType() +" " +nextAction.getParameter());
	
	switch( nextAction.getVoteType() ){
	case FIRE:
	    setFire( nextAction.getParameter() );
	    break;
	    
	case MOVE_AHEAD:
	    setAhead( nextAction.getParameter() );
	    break;
	    
	case MOVE_BACK:
	    setBack( nextAction.getParameter() );
	    break;
	    
	case SEND_MESSAGE:
	    try {
		sendMessage(nextAction.getRecipientName(), nextAction.getMessage() );
	    } catch (IOException e) {
		logger.severe("Failed to send message to :" + nextAction.getRecipientName());
	    }	
	    break;
	    
	case BROADCAST_MESSAGE:
	    try {
		broadcastMessage( nextAction.getMessage() );
	    } catch (IOException e) {
		logger.severe("Failed to broadcast a message");
	    }
	    break;
	    
	case TURN_RIGHT_DEGREES:
	    setTurnRight( nextAction.getParameter() );
	    break;
	    
	case TURN_LEFT_DEGREES:
	    setTurnLeft( nextAction.getParameter() );
	    break;
	    
	case TURN_RIGHT_RADIANS:
	    setTurnRightRadians( nextAction.getParameter() );
	    break;
	    
	case TURN_LEFT_RADIANS:
	    setTurnLeftRadians( nextAction.getParameter() );
	    break;
	    
	case TURN_GUN_RIGHT_DEGREES:
	    setTurnGunRight( nextAction.getParameter() );
	    break;
	    
	case TURN_GUN_RIGHT_RADIANS:
	    setTurnGunRightRadians( nextAction.getParameter() );
	    break;
	    
	case TURN_GUN_LEFT_DEGREES:
	    setTurnGunLeft( nextAction.getParameter() );
	    break;
	    
	case TURN_GUN_LEFT_RADIANS:
	    setTurnGunLeftRadians( nextAction.getParameter() );
	    break; 
	
	case TURN_RADAR_LEFT_RADIANS:
	    setTurnRadarLeftRadians( nextAction.getParameter() );
	    break;
	    
	case TURN_RADAR_RIGHT_RADIANS:
	    setTurnRadarRightRadians( nextAction.getParameter() );
	    break;   
	    
	case TURN_RADAR_LEFT_DEGREES:
	    setTurnRadarLeft( nextAction.getParameter() );
	    break;
	    
	case TURN_RADAR_RIGHT_DEGREES:
	    setTurnRadarRight( nextAction.getParameter() );
	    break;   
	}
    }


    /**
     * 	This method is called after the end of the battle.
     */
    @Override
    public void onBattleEnded(final BattleEndedEvent event){
	Runnable updateTask = new Runnable(){
	    @Override
	    public void run() {
		for (IEventResponder responder : lEventResponders){
		    responder.onBattleEnded( event );
		}
	    }
	};
	sendUpdate( updateTask  );
	stopAllCoordinators();
    }




    /**
     * 	This method is called when one of your bullets hits another robot.
     */
    @Override
    public void onBulletHit(final BulletHitEvent event){
	Runnable updateTask = new Runnable(){
	    @Override
	    public void run() {
		for (IEventResponder responder : lEventResponders){
		    responder.onBulletHit( event );
		}
	    }
	};
	sendUpdate( updateTask  );
    }

    /**
     * 	This method is called when one of your bullets hits another bullet.
     */
    @Override
    public void onBulletHitBullet(final BulletHitBulletEvent event){
	Runnable updateTask = new Runnable(){
	    @Override
	    public void run() {
		for (IEventResponder responder : lEventResponders){
		    responder.onBulletHitBullet( event );
		}
	    }
	};
	sendUpdate( updateTask  );
    }

    /**
     * 	This method is called when one of your bullets misses.
     */
    @Override
    public void onBulletMissed(final BulletMissedEvent event){
	Runnable updateTask = new Runnable(){
	    @Override
	    public void run() {
		for (IEventResponder responder : lEventResponders){
		    responder.onBulletMissed( event );
		}
	    }
	};
	sendUpdate( updateTask  );
    }

    /**
     * 	This method is called if your robot dies.
     */
    @Override
    public void onDeath(final DeathEvent event){
	Runnable updateTask = new Runnable(){
	    @Override
	    public void run() {
		for (IEventResponder responder : lEventResponders){
		    responder.onDeath( event );
		}
	    }
	};
	sendUpdate( updateTask  );
	stopAllCoordinators();
    }

    /**
     * 	This method is called when your robot is hit by a bullet.
     */
    @Override
    public void onHitByBullet(final HitByBulletEvent event){
	Runnable updateTask = new Runnable(){
	    @Override
	    public void run() {
		for (IEventResponder responder : lEventResponders){
		    responder.onHitByBullet( event );
		}
	    }
	};
	sendUpdate( updateTask  );
    }

    /**
     * 	This method is called when your robot collides with another robot.
     */
    @Override
    public void onHitRobot(final HitRobotEvent event){
	Runnable updateTask = new Runnable(){
	    @Override
	    public void run() {
		for (IEventResponder responder : lEventResponders){
		    responder.onHitRobot( event );
		}
	    }
	};
	sendUpdate( updateTask  );
    }

    /**
     * 	This method is called when your robot collides with a wall.
     */
    @Override
    public void onHitWall(final HitWallEvent event){
	Runnable updateTask = new Runnable(){
	    @Override
	    public void run() {
		for (IEventResponder responder : lEventResponders){
		    responder.onHitWall( event );
		}
	    }
	};
	sendUpdate( updateTask  );
    }
    @Override
    public void onCustomEvent(final CustomEvent e) {
	Runnable updateTask = new Runnable(){
	    @Override
	    public void run() {
		for (IEventResponder responder : lEventResponders){
		    responder.onCustomEvent( e );
		}
	    }
	};
	sendUpdate( updateTask  );
    }	
	
    /**
     * 	This method is called when another robot dies.
     */
    @Override
    public void onRobotDeath(final RobotDeathEvent event){
	Runnable updateTask = new Runnable(){
	    @Override
	    public void run() {
		for (IEventResponder responder : lEventResponders){
		    responder.onRobotDeath( event );
		}
	    }
	};
	sendUpdate( updateTask  );
    }

    /**
     * 	This method is called after the end of a round.
     */
    @Override
    public void onRoundEnded(final RoundEndedEvent event){
	Runnable updateTask = new Runnable(){
	    @Override
	    public void run() {
		for (IEventResponder responder : lEventResponders){
		    responder.onRoundEnded( event );
		}
	    }
	};
	sendUpdate( updateTask  );
	stopAllCoordinators();
    }




    /**
     * 	This method is called when your robot sees another robot.
     */
    @Override
    public void onScannedRobot(final ScannedRobotEvent event){
	Runnable updateTask = new Runnable(){
	    @Override
	    public void run() {
		for (IEventResponder responder : lEventResponders){
		    responder.onScannedRobot( event );
		}
	    }
	};
	sendUpdate( updateTask  );
    }

    /**
     * 	This method is called every turn in a battle round in order to provide the robot status 
     * 	as a complete snapshot of the robot's current state at that specific time.
     */
    @Override
    public void onStatus(final StatusEvent event){
/*	Runnable updateTask = new Runnable(){
	    @Override
	    public void run() {
		for (IEventResponder responder : lEventResponders){
		    responder.onStatus( event );
		}
	    }
	};
	sendUpdate( updateTask  );
*/
    }

    /**
     * 	This method is called if your robot wins a battle
     */
    @Override
    public void onWin(final WinEvent event){
	Runnable updateTask = new Runnable(){
	    @Override
	    public void run() {
		for (IEventResponder responder : lEventResponders){
		    responder.onWin( event );
		}
	    }
	};
	sendUpdate( updateTask  );
    }

    @Override
    public void onMessageReceived(final MessageEvent MessageEvent){
	Runnable updateTask = new Runnable(){
	    @Override
	    public void run() {
		for (IEventResponder responder : lEventResponders){
		    responder.onMessageReceived( MessageEvent );
		}
	    }
	};
	sendUpdate( updateTask  );
    }
    

    @Override
    public BThreadVote getNextVote() {
	
	BThreadVote nextVote = getNextAllowedVote();
	if ( nextVote == null ){ return null;}
	deleteRedundantVotes(lVotes, nextVote);
	return nextVote;
    }

    private void deleteRedundantVotes(Collection<BThreadVote> lv, BThreadVote nextVote) {
	
 	List<BThreadVote> lReduced = new LinkedList<BThreadVote>();
 	for (BThreadVote vote : lv){
 	    if (vote.getVoteType().equals( nextVote.getVoteType() )){
 		lReduced.add( vote );
 	    }
 	}
 	lv.removeAll( lReduced );
     }
    private BThreadVote getNextAllowedVote() {
	
	BThreadVote nextVote = lVotes.poll();
	List<BThreadVote> lReduced = new LinkedList<BThreadVote>();
	while ( blockedVotes.contains( nextVote )){
	    lReduced.add( nextVote );
	    nextVote = lVotes.poll();
	}
	blockedVotes.removeAll( lReduced );
	return nextVote;
    }


    @Override
    public void vote(BThreadVote vote) {
	
	vote(vote, BThreadVoteType.ALLOW);
	
	if (vote == null || vote.getVoteType().equals( BThreadType.DO_NOTHING )){ return; }
	
	try{
	    synchronizationLock.lock();	
	    lVotes.add( vote );
	    synchronizationLock.unlock();
	}catch (Throwable t){}
    }

    

    @Override
    public void stop() {
	isActive = false;
	
    }
    
    private void sendUpdate(Runnable updateTask) {
	ThreadManager.getInstance().submit( "Update Task", updateTask );
    }


    @Override
    public int getLevel() {
	return 2;
    }

    
    private void stopAllCoordinators() {
	
	logger.info("Stoping all Coordinators");
	for (ICoordinator<BThreadVote> coordinator : lCoordinators){
	    coordinator.stop();
	}
	stop();
	
    }


    @Override
    public ITankInfo getTankInfo() {
	return new TankInfoImpl(this);
    }


    @Override
    public void vote(BThreadVote vote, BThreadVoteType voteType) {
	if (vote == null || vote.getVoteType().equals( BThreadType.DO_NOTHING )){ return; }
	
	try{
	    synchronizationLock.lock();
	    switch( voteType){
	    case ALLOW:
		lVotes.add( vote );
		break;
	    case BLOCK:
		blockedVotes.add( vote );
		break;
	    }
	    
	    synchronizationLock.unlock();
	}catch (Throwable t){}
	
    }


}
